package nachos.threads;

/**
 * A <i>communicator</i> allows threads to synchronously exchange 32-bit
 * messages. Multiple threads can be waiting to <i>speak</i>,
 * and multiple threads can be waiting to <i>listen</i>. But there should never
 * be a time when both a speaker and a listener are waiting, because the two
 * threads can be paired off at this point.
 */
public class Communicator {
	
	/**
	 * instance variables
	 */
	private int transferWord;
	private Lock lock;
	private boolean activeSpeaker, activeListener;
	private Condition2 speakersWFT, listenersWFT,
		speakerWaitingForTransferCompletion, listenerWaitingForData;
	
	
	/**
	 * Allocate a new communicator.
	 */
	public Communicator() {
		lock = new Lock();
		speakersWFT = new Condition2 (lock);
		listenersWFT = new Condition2 (lock);
		speakerWaitingForTransferCompletion = new Condition2 (lock);
		listenerWaitingForData = new Condition2 (lock);
	}

	/**
	 * Wait for a thread to listen through this communicator, and then transfer
	 * <i>word</i> to the listener.
	 *
	 * <p>
	 * Does not return until this thread is paired up with a listening thread.
	 * Exactly one listener should receive <i>word</i>.
	 *
	 * @param	word	the integer to transfer.
	 */
	public void speak(int word) {
		lock.acquire();
		
		while (activeSpeaker)
			speakersWFT.sleep();
		
		activeSpeaker = true;
		transferWord = word;
		listenerWaitingForData.wake();
		
		speakerWaitingForTransferCompletion.sleep();
		
		activeSpeaker = false;
		speakersWFT.wake();
		lock.release();
	}

	/**
	 * Wait for a thread to speak through this communicator, and then return
	 * the <i>word</i> that thread passed to <tt>speak()</tt>.
	 *
	 * @return	the integer transferred.
	 */    
	public int listen() {
		lock.acquire();
		
		while (activeListener)
			listenersWFT.sleep();
		
		activeListener = true;
		while (!activeSpeaker)
			listenerWaitingForData.sleep();
		
		int wordToReturn = transferWord;
		
		speakerWaitingForTransferCompletion.wake();
		activeListener = false;
		listenersWFT.wake();
		lock.release();
		return wordToReturn;
	}
	
	
	static void selfTest() {
	    
	    System.out.println("*****COMM TESTS*****");
	    
	    final Communicator com = new Communicator();
	    final int testWord = 678;
	    
	    final KThread t1 = new KThread (new Runnable () {
	        public void run() {
	            System.out.println("T1 speaks "+testWord);
	            com.speak(testWord);
	            System.out.println("T1 returns from speak()");
	        }
	    });
	    
	    final KThread t2 = new KThread (new Runnable () {
	        public void run() {
	            System.out.println("T2 listens...");
	            int rtn = com.listen();
	            System.out.println("T2 returned: "+rtn);
	        }
	    });
	    
	    final KThread t4 = new KThread (new Runnable () {
            public void run() {
                System.out.println("T4 speaks "+testWord);
                com.speak(testWord);
                System.out.println("T4 returns from speak()");
            }
        });
        
        final KThread t3 = new KThread (new Runnable () {
            public void run() {
                System.out.println("T3 listens...");
                int rtn = com.listen();
                System.out.println("T3 returned: "+rtn);
            }
        });
	    
        final KThread t5 = new KThread (new Runnable () {
            public void run() {
                System.out.println("T5 speaks "+testWord);
                com.speak(testWord);
                System.out.println("T5 returns from speak()");
            }
        });
        
        final KThread t6 = new KThread (new Runnable () {
            public void run() {
                System.out.println("T6 listens...");
                int rtn = com.listen();
                System.out.println("T6 returned: "+rtn);
            }
        });
        
        final KThread t7 = new KThread (new Runnable () {
            public void run() {
                System.out.println("T7 speaks "+1000+", should be waiting");
                com.speak(1000);
                System.out.println("T7 returns from speak()");
            }
        });
        
        final KThread t8 = new KThread (new Runnable () {
            public void run() {
                System.out.println("T8 listens...");
                int rtn = com.listen();
                System.out.println("T8 returned: "+rtn);
            }
        });
        
	    /**
	     * the following speaks 678 from t1 to t2. speak() is called first.
	     */
	    t1.fork();
	    while (t1.getStatus() != KThread.statusBlocked)
	        KThread.yield();
	    // assertion: t1 has now called speak() and is waiting for a listener
	    t2.fork();
	    t1.join();
	    t2.join();
	    // assertion: t1 & t2 have spoken & listened, and have returned
	    
	    /**
	     * this calls listen() first, then speak()
	     */
	    System.out.println("---");
	    t3.fork();
	    while (t3.getStatus() != KThread.statusBlocked)
	        KThread.yield();
	    // assertion: t3 is sleeping as the listener, waiting for a speaker
	    t4.fork();
	    t3.join();
	    t4.join();
	    // assertion: t4 has spoken to t3, both have returned
	    
	    /**
	     * lastly, we check for multiple speakers/listeners
	     */
	    System.out.println("---");
	    t5.fork();
	    while (t5.getStatus() != KThread.statusBlocked)
	        KThread.yield();
	    // assertion: t5 is waiting for a listener
	    t7.fork();
	    while (t7.getStatus() != KThread.statusBlocked)
	        KThread.yield();
	    // assertion: t7 is waiting behind t5 to speak
	    t6.fork();
	    t5.join();
	    t6.join();
	    // assertion: t5 -> t6, t7 is now waiting for a listener
	    t8.fork();
	    t7.join();
	    t8.join();
	    // assertion: t7 -> t8, everthing has finished & returned
	    
	    System.out.println("****COMM TESTS FINISHED*****\n");
	}
}
